/******************************************************************************
 *    Copyright 2013 IT2media - IT-Solutions for Directory Business           *
 *                                                                            *
 *  Licensed under the Apache License, Version 2.0 (the "License");           *
 *  you may not use this file except in compliance with the License.          *
 *  You may obtain a copy of the License at                                   *
 *                                                                            *
 *      http://www.apache.org/licenses/LICENSE-2.0                            *
 *                                                                            *
 *  Unless required by applicable law or agreed to in writing, software       *
 *  distributed under the License is distributed on an "AS IS" BASIS,         *
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *
 *  See the License for the specific language governing permissions and       *
 *  limitations under the License.                                            *
 ******************************************************************************/
package de.it2media.javasitemapgenerator.url.extensions;

import java.net.MalformedURLException;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import de.it2media.javasitemapgenerator.StringJavaSitemapGenerator;
import de.it2media.javasitemapgenerator.header.XMLNS;

/**
 * Builds up a video sitemap part like in https://support.google.com/webmasters/answer/80472?hl=en
 * 
 * @ToDo - you can set an attribute without setting the tag - problem?
 * 
 * @author igloffsteinma
 * 
 */
public class JavaSitemapGeneratorVideoURL extends AbstractURLExtension {

  protected final String     VIDEO_THUMBNAIL_LOC          = "thumbnail_loc";
  protected final String     VIDEO_TITLE                  = "title";
  protected final String     VIDEO_DESCRIPTION            = "description";
  protected final String     VIDEO_CONTENT_LOC            = "content_loc";
  protected final String     VIDEO_PLAYER_LOC             = "player_loc";
  protected final String     VIDEO_DURATION               = "duration";
  protected final String     VIDEO_EXPIRATION_DATE        = "expiration_date";
  protected final String     VIDEO_RATING                 = "rating";
  protected final String     VIDEO_VIEW_COUNT             = "view_count";
  protected final String     VIDEO_PUBLICATION_DATE       = "publication_date";
  protected final String     VIDEO_FAMILY_FRIENDLY        = "family_friendly";
  protected final String     VIDEO_TAG                    = "tag";
  protected final String     VIDEO_CATEGORY               = "category";
  protected final String     VIDEO_RESTRICTION            = "restriction";
  protected final String     VIDEO_GALLERY_LOC            = "gallery_loc";
  protected final String     VIDEO_PRICE                  = "price";
  protected final String     VIDEO_REQUIRES_SUBSCRIPTION  = "requires_subscription";
  protected final String     VIDEO_UPLOADER               = "uploader";
  protected final String     VIDEO_PLATFORM               = "platform";
  protected final String     VIDEO_LIVE                   = "live";

  public final static int    VIDEO_TITLE_MAX_LENGTH       = 100;
  public final static int    VIDEO_DESCRIPTION_MAX_LENGTH = 2048;
  public final static int    VIDEO_DURATION_MAX_VALUE     = 28800;
  public final static float  VIDEO_RATING_MAX_VALUE       = 5.0f;
  public final static int    VIDEO_TAG_MAX_COUNT          = 32;
  public final static int    VIDEO_CATEGORY_MAX_LENGTH    = 256;

  public final static String CDATA_START                  = "<![CDATA[";
  public final static String CDATA_END                    = "]]>";

  public enum VIDEO_RESTRICTION_RELATIONSHIP {
    ALLOW, DENY
  }

  public enum VIDEO_PLATFORM_TAGS {
    WEB, MOBILE, TV
  }

  public enum VIDEO_PRICE_TYPE {
    RENT, OWN
  }

  public enum VIDEO_PRICE_RESOLUTION {
    HD, SD
  }

  /**
   * A map that stores the attributes<br>
   * e.g.: &lt;video:player_loc allow_embed="yes" autoplay="ap=1"&gt;http://www.example.com/videoplayer.swf?video=123&lt;/video:player_loc&gt;<br>
   * --> the map will store: allow_embed=attributes"yes" autoplay="ap=1"
   */
  protected Map<String, String>  attributes       = new LinkedHashMap<String, String>();

  /**
   * Because of the multiple price elements, we have to create a new list
   */
  protected List<String>         prices           = null;
  protected List<String>         pricesAttributes = null;

  /**
   * To format the price always correct
   */
  protected static DecimalFormat priceFormat      = new DecimalFormat("#0.00");

  /**
   * Generates a video sitemap part like:<br>
   * 
   * <pre>
   *   <code>
   *     &lt;video&gt;
   *       &lt;video:thumbnail_loc&gt;http://www.example.com/thumbs/123.jpg&lt;/video:thumbnail_loc&gt; 
   *       &lt;video:title&gt;Grilling steaks for summer&lt;/video:title&gt;
   *       &lt;video:description&gt;Alkis shows you how to get perfectly done steaks every time&lt;/video:description&gt;
   *       &lt;video:content_loc&gt;http://www.example.com/video123.flv&lt;/video:content_loc&gt;
   *     &lt;/video&gt;
   *   </code>
   * </pre>
   * 
   * <br>
   * 
   * @param thumbnailLocation - <b>not null</b> - A URL pointing to the video thumbnail image file. Images must be at least 160 x 90 pixels and at most 1920x1080 pixels. We recommend images in .jpg, .png, or. gif formats.
   * @param title - <b>not null</b> - The title of the video. Maximum 100 characters. The title must be in plain text only, and any HTML entities should be escaped or wrapped in a CDATA block.
   * @param description - <b>not null</b> - The description of the video. Maximum 2048 characters. The description must be in plain text only, and any HTML entities should be escaped or wrapped in a CDATA block.
   * @param contentLocation - <b>if null:</b> playerLocation have to be defined - A URL pointing to the actual video media file. This file should be in .mpg, .mpeg, .mp4, .m4v, .mov, .wmv, .asf, .avi, .ra, .ram, .rm, .flv, or other video file format. Providing this file allows Google to generate video thumbnails and video previews, and can help Google verify your video. Best practice: Ensure that only Googlebot accesses your content by using a reverse DNS lookup.
   * @param playerLocation - <b>if null:</b> contentLocation have to be defined - A URL pointing to a player for a specific video. Usually this is the information in the src element of an &lt;embed&gt; tag and should not be the same as the content of the &lt;loc&gt; tag. ​ The optional attribute allow_embed specifies whether Google can embed the video in search results. Allowed values are Yes or No. The optional attribute autoplay has a user-defined string (in the example above, ap=1) that Google may append (if appropriate) to the flashvars parameter to enable autoplay of the video. For example: &lt;embed src="http://www.example.com/videoplayer.swf?video=123" autoplay="ap=1"/&gt;. (Best practice: Ensure that only Googlebot accesses your content by using a reverse DNS lookup.)
   */
  public JavaSitemapGeneratorVideoURL(URL thumbnailLocation, String title, String description, URL contentLocation, URL playerLocation) {

    // video:content_loc && video:player_loc - only one of them is required!
    // so we have to check if one of them is there!
    if (contentLocation == null & playerLocation == null) {
      throw new RuntimeException("You must specify at least one of <video:player_loc> (playerLocation) or <video:content_loc> (contentLocation). Look to: https://support.google.com/webmasters/answer/80472?hl=en");
    }

    // add content
    // @todo - split cdata block if we got ]]> or <![CDATA[
    setThumnailLoc(thumbnailLocation);
    setTitle(title);
    setDescription(description);
    setContentLoc(contentLocation);
    setPlayerLoc(playerLocation);

    // we need points as the separator symbol!
    priceFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.ENGLISH));
  }

  /**
   * Generates a video sitemap part like:<br>
   * 
   * <pre>
   *   <code>
   *     &lt;video&gt;
   *       &lt;video:thumbnail_loc&gt;http://www.example.com/thumbs/123.jpg&lt;/video:thumbnail_loc&gt; 
   *       &lt;video:title&gt;Grilling steaks for summer&lt;/video:title&gt;
   *       &lt;video:description&gt;Alkis shows you how to get perfectly done steaks every time&lt;/video:description&gt;
   *       &lt;video:content_loc&gt;http://www.example.com/video123.flv&lt;/video:content_loc&gt;
   *     &lt;/video&gt;
   *   </code>
   * </pre>
   * 
   * <br>
   * 
   * @param thumbnailLocation - <b>not null</b> - A URL pointing to the video thumbnail image file. Images must be at least 160 x 90 pixels and at most 1920x1080 pixels. We recommend images in .jpg, .png, or. gif formats.
   * @param title - <b>not null</b> - CDATA Block is automatically added <br>
   *          The title of the video. Maximum 100 characters. The title must be in plain text only, and any HTML entities should be escaped or wrapped in a CDATA block.
   * @param description - <b>not null</b> - CDATA Block is automatically added <br>
   *          The description of the video. Maximum 2048 characters. The description must be in plain text only, and any HTML entities should be escaped or wrapped in a CDATA block.
   * @param contentLocation - <b>if null:</b> playerLocation have to be defined - A URL pointing to the actual video media file. This file should be in .mpg, .mpeg, .mp4, .m4v, .mov, .wmv, .asf, .avi, .ra, .ram, .rm, .flv, or other video file format. Providing this file allows Google to generate video thumbnails and video previews, and can help Google verify your video. Best practice: Ensure that only Googlebot accesses your content by using a reverse DNS lookup.
   * @param playerLocation - <b>if null:</b> contentLocation have to be defined - A URL pointing to a player for a specific video. Usually this is the information in the src element of an &lt;embed&gt; tag and should not be the same as the content of the &lt;loc&gt; tag. ​ The optional attribute allow_embed specifies whether Google can embed the video in search results. Allowed values are Yes or No. The optional attribute autoplay has a user-defined string (in the example above, ap=1) that Google may append (if appropriate) to the flashvars parameter to enable autoplay of the video. For example: &lt;embed src="http://www.example.com/videoplayer.swf?video=123" autoplay="ap=1"/&gt;. (Best practice: Ensure that only Googlebot accesses your content by using a reverse DNS lookup.)
   */
  public JavaSitemapGeneratorVideoURL(String thumbnailLocation, String title, String description, String contentLocation, String playerLocation) throws MalformedURLException {
    this(new URL(thumbnailLocation), title, description, (contentLocation != null) ? new URL(contentLocation) : null, (playerLocation != null) ? new URL(playerLocation) : null);
  }

  /**
   * A URL pointing to the video thumbnail image file. Images must be at least 160 x 90 pixels and at most 1920x1080 pixels. We recommend images in .jpg, .png, or. gif formats.
   * 
   * @return
   */
  public URL getThumbnailLoc() {
    return (URL) content.get(VIDEO_THUMBNAIL_LOC);
  }

  /**
   * A URL pointing to the video thumbnail image file. Images must be at least 160 x 90 pixels and at most 1920x1080 pixels. We recommend images in .jpg, .png, or. gif formats.
   * 
   * @return
   */
  public URL getThumbnailLocation() {
    return getThumbnailLoc();
  }

  /**
   * Sets the thumbnail location from the constructor<br>
   * For more details look to: https://support.google.com/webmasters/answer/80472?hl=en
   * 
   * @param thumbnailLocation
   * @return
   */
  protected JavaSitemapGeneratorVideoURL setThumnailLoc(URL thumbnailLocation) {
    // thumbnail location can't be null
    if (thumbnailLocation == null) {
      throw new RuntimeException("ThumbnailLocation is null");
    }
    content.put(VIDEO_THUMBNAIL_LOC, thumbnailLocation);
    return this;
  }

  /**
   * The title of the video. Maximum 100 characters. The title must be in plain text only, and any HTML entities should be escaped or wrapped in a CDATA block.
   * 
   * @return "<![CDATA[" + title + "]]>" or title
   */
  public String getTitle() {
    return (String) content.get(VIDEO_TITLE);
  }

  /**
   * Sets the title for the video<br>
   * For more details look to: https://support.google.com/webmasters/answer/80472?hl=en
   * 
   * @param title
   * @return
   */
  protected JavaSitemapGeneratorVideoURL setTitle(String title) {
    // title can't be null or ""
    if (title == null || "".equals(title)) {
      throw new RuntimeException("Title is: null or \"\"");
    }
    // check if we stay to the google limits
    if (!check(getCDataString(title).length(), VIDEO_TITLE_MAX_LENGTH)) {
      throw new RuntimeException("Title with " + getCDataString(title).length() + " characters invalid: " + getCDataString(title) + ", max title length: " + VIDEO_TITLE_MAX_LENGTH);
    }
    // everything is valid - so add
    content.put(VIDEO_TITLE, getCDataString(title));
    return this;
  }

  /**
   * The description of the video. Maximum 2048 characters. The description must be in plain text only, and any HTML entities should be escaped or wrapped in a CDATA block.
   * 
   * @return "<![CDATA[" + description + "]]>"
   */
  public String getDescription() {
    return (String) content.get(VIDEO_DESCRIPTION);
  }

  /**
   * Sets the description for the video<br>
   * For more details look to: https://support.google.com/webmasters/answer/80472?hl=en
   * 
   * @param description
   * @return
   */
  protected JavaSitemapGeneratorVideoURL setDescription(String description) {

    // description can't be null or ""
    if (description == null || "".equals(description)) {
      throw new RuntimeException("Description is: null or \"\"");
    }
    // check if we stay to the google limits
    if (!check(getCDataString(description).length(), VIDEO_DESCRIPTION_MAX_LENGTH)) {
      throw new RuntimeException("Description with " + getCDataString(description).length() + " characters invalid: " + getCDataString(description) + ", max description length: " + VIDEO_DESCRIPTION_MAX_LENGTH);
    }
    // everything is valid - so add
    content.put(VIDEO_DESCRIPTION, getCDataString(description));
    return this;
  }

  /**
   * You must specify at least one of &lt;video:player_loc&gt; or &lt;video:content_loc&gt;.<br>
   * A URL pointing to the actual video media file. This file should be in .mpg, .mpeg, .mp4, .m4v, .mov, .wmv, .asf, .avi, .ra, .ram, .rm, .flv, or other video file format.<br>
   * <br>
   * Providing this file allows Google to generate video thumbnails and video previews, and can help Google verify your video.<br>
   * <br>
   * Best practice: Ensure that only Googlebot accesses your content by using a reverse DNS lookup.<br>
   * 
   * @return
   */
  public URL getContentLoc() {
    return (URL) content.get(VIDEO_CONTENT_LOC);
  }

  /**
   * You must specify at least one of &lt;video:player_loc&gt; or &lt;video:content_loc&gt;.<br>
   * A URL pointing to the actual video media file. This file should be in .mpg, .mpeg, .mp4, .m4v, .mov, .wmv, .asf, .avi, .ra, .ram, .rm, .flv, or other video file format.<br>
   * <br>
   * Providing this file allows Google to generate video thumbnails and video previews, and can help Google verify your video.<br>
   * <br>
   * Best practice: Ensure that only Googlebot accesses your content by using a reverse DNS lookup.<br>
   * 
   * @return
   */
  public URL getContentLocation() {
    return getContentLoc();
  }

  /**
   * Sets the content location for the video<br>
   * For more details look to: https://support.google.com/webmasters/answer/80472?hl=en
   * 
   * @param contentLocation
   * @return
   */
  protected JavaSitemapGeneratorVideoURL setContentLoc(URL contentLocation) {
    if (contentLocation != null)
      content.put(VIDEO_CONTENT_LOC, contentLocation);

    return this;
  }

  /**
   * You must specify at least one of &lt;video:player_loc&gt; or &lt;video:content_loc&gt;.<br>
   * A URL pointing to a player for a specific video. Usually this is the information in the src element of an &lt;embed&gt; tag and should not be the same as the content of the &lt;loc&gt; tag. ​<br>
   * <br>
   * The optional attribute allow_embed specifies whether Google can embed the video in search results. Allowed values are Yes or No.<br>
   * <br>
   * The optional attribute autoplay has a user-defined string (in the example above, ap=1) that Google may append (if appropriate) to the flashvars parameter to enable autoplay of the video. For example: &lt;embed src="http://www.example.com/videoplayer.swf?video=123" autoplay="ap=1"/&gt;.<br>
   * <br>
   * Example:<br>
   * <br>
   * Dailymotion: http://www.dailymotion.com/swf/x1o2g<br>
   * <br>
   * Best practice: Ensure that only Googlebot accesses your content by using a reverse DNS lookup.<br>
   * 
   * @return
   */
  public URL getPlayerLoc() {
    return (URL) content.get(VIDEO_PLAYER_LOC);
  }

  /**
   * You must specify at least one of &lt;video:player_loc&gt; or &lt;video:content_loc&gt;.<br>
   * A URL pointing to a player for a specific video. Usually this is the information in the src element of an &lt;embed&gt; tag and should not be the same as the content of the &lt;loc&gt; tag. ​<br>
   * <br>
   * The optional attribute allow_embed specifies whether Google can embed the video in search results. Allowed values are Yes or No.<br>
   * <br>
   * The optional attribute autoplay has a user-defined string (in the example above, ap=1) that Google may append (if appropriate) to the flashvars parameter to enable autoplay of the video. For example: &lt;embed src="http://www.example.com/videoplayer.swf?video=123" autoplay="ap=1"/&gt;.<br>
   * <br>
   * Example:<br>
   * <br>
   * Dailymotion: http://www.dailymotion.com/swf/x1o2g<br>
   * <br>
   * Best practice: Ensure that only Googlebot accesses your content by using a reverse DNS lookup.<br>
   * 
   * @return
   */
  public URL getPlayerLocation() {
    return getPlayerLoc();
  }

  /**
   * Sets the player location for the video<br>
   * For more details look to: https://support.google.com/webmasters/answer/80472?hl=en
   * 
   * You must specify at least one of &lt;video:player_loc&gt; or &lt;video:content_loc&gt;.<br>
   * A URL pointing to a player for a specific video. Usually this is the information in the src element of an &lt;embed&gt; tag and should not be the same as the content of the &lt;loc&gt; tag. ​<br>
   * <br>
   * The optional attribute allow_embed specifies whether Google can embed the video in search results. Allowed values are Yes or No.<br>
   * <br>
   * The optional attribute autoplay has a user-defined string (in the example above, ap=1) that Google may append (if appropriate) to the flashvars parameter to enable autoplay of the video. For example: &lt;embed src="http://www.example.com/videoplayer.swf?video=123" autoplay="ap=1"/&gt;.<br>
   * <br>
   * Example:<br>
   * <br>
   * Dailymotion: http://www.dailymotion.com/swf/x1o2g<br>
   * <br>
   * Best practice: Ensure that only Googlebot accesses your content by using a reverse DNS lookup.<br>
   * 
   * @param playerLocation
   * @return
   */
  protected JavaSitemapGeneratorVideoURL setPlayerLoc(URL playerLocation) {
    if (playerLocation != null)
      content.put(VIDEO_PLAYER_LOC, playerLocation);

    return this;
  }

  /**
   * The optional attribute allow_embed specifies whether Google can embed the video in search results. Allowed values are Yes or No.<br>
   * 
   * @param allowEmbed
   * @return
   */
  public JavaSitemapGeneratorVideoURL setPlayerLocAllowEmbed(boolean allowEmbed) {
    if (!content.containsKey(VIDEO_PLAYER_LOC)) {
      throw new RuntimeException("player_loc not set - use player_loc to allow embed");
    }
    // writes the attribute
    attributes.put(VIDEO_PLAYER_LOC, getAttributeContentFor(VIDEO_PLAYER_LOC, "allow_embed", ((allowEmbed) ? "yes" : "no")));
    return this;
  }

  /**
   * The optional attribute allow_embed specifies whether Google can embed the video in search results. Allowed values are Yes or No.<br>
   * 
   * @param allowEmbed
   * @return
   */
  public JavaSitemapGeneratorVideoURL setPlayerLocationAllowEmbed(boolean allowEmbed) {
    return setPlayerLocAllowEmbed(allowEmbed);
  }

  /**
   * The optional attribute autoplay has a user-defined string (in the example above, ap=1) that Google may append (if appropriate) to the flashvars parameter to enable autoplay of the video. For example: &lt;embed src="http://www.example.com/videoplayer.swf?video=123" autoplay="ap=1"/&gt;.
   * 
   * @param autoplay
   * @return
   */
  public JavaSitemapGeneratorVideoURL setPlayerLocAutoplay(String autoplay) {
    if (!content.containsKey(VIDEO_PLAYER_LOC)) {
      throw new RuntimeException("player_loc not set - use player_loc to allow embed");
    }
    // throw an exception if we got invalid content
    checkInput(autoplay);
    // sets the attribute autoplay
    attributes.put(VIDEO_PLAYER_LOC, getAttributeContentFor(VIDEO_PLAYER_LOC, "autoplay", autoplay));
    return this;
  }

  /**
   * The optional attribute autoplay has a user-defined string (in the example above, ap=1) that Google may append (if appropriate) to the flashvars parameter to enable autoplay of the video. For example: &lt;embed src="http://www.example.com/videoplayer.swf?video=123" autoplay="ap=1"/&gt;.
   * 
   * @param autoplay
   * @return
   */
  public JavaSitemapGeneratorVideoURL setPlayerLocationAutoplay(String autoplay) {
    return setPlayerLocAutoplay(autoplay);
  }

  /**
   * Returns the player location attributes
   * The optional attribute allow_embed specifies whether Google can embed the video in search results. Allowed values are Yes or No.<br>
   * The optional attribute autoplay has a user-defined string (in the example above, ap=1) that Google may append (if appropriate) to the flashvars parameter to enable autoplay of the video. For example: &lt;embed src="http://www.example.com/videoplayer.swf?video=123" autoplay="ap=1"/&gt;.
   * 
   * @return
   */
  public String getPlayerLocAttributes() {
    return attributes.get(VIDEO_PLAYER_LOC);
  }

  /**
   * Returns the player location attributes
   * The optional attribute allow_embed specifies whether Google can embed the video in search results. Allowed values are Yes or No.<br>
   * The optional attribute autoplay has a user-defined string (in the example above, ap=1) that Google may append (if appropriate) to the flashvars parameter to enable autoplay of the video. For example: &lt;embed src="http://www.example.com/videoplayer.swf?video=123" autoplay="ap=1"/&gt;.
   * 
   * @return
   */
  public String getPlayerLocationAttributes() {
    return getPlayerLocAttributes();
  }

  /**
   * Removes all attributes for PlayerLocation
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removePlayerLocAttributes() {
    attributes.remove(VIDEO_PLAYER_LOC);
    return this;
  }

  /**
   * Removes all attributes for PlayerLocation
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removePlayerLocationAttributes() {
    return removePlayerLocAttributes();
  }

  /**
   * The duration of the video in seconds. Value must be between 0 and 28800 (8 hours).
   * 
   * @return
   */
  public Integer getDuration() {
    return (Integer) content.get(VIDEO_DURATION);
  }

  /**
   * The duration of the video in seconds. Value must be between 0 and 28800 (8 hours).
   * 
   * @param duration
   * @return
   */
  public JavaSitemapGeneratorVideoURL setDuration(int duration) {
    if (!check(duration, VIDEO_DURATION_MAX_VALUE)) {
      throw new RuntimeException("Duration with " + duration + " seconds invalid: " + duration);
    }
    // everything is valid - so add
    content.put(VIDEO_DURATION, new Integer(duration));
    return this;
  }

  /**
   * Removes duration form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeDuration() {
    content.remove(VIDEO_DURATION);
    return this;
  }

  /**
   * The date after which the video will no longer be available, in W3C format. Don't supply this information if your video does not expire.
   * 
   * @return
   */
  public Date getExpirationDate() {
    return (Date) content.get(VIDEO_EXPIRATION_DATE);
  }

  /**
   * The date after which the video will no longer be available, in W3C format. Don't supply this information if your video does not expire.
   * 
   * @ToDo check if this lies in the future
   * 
   * @param expirationDate
   * @return
   */
  public JavaSitemapGeneratorVideoURL setExpirationDate(Date expirationDate) {
    if (expirationDate == null) {
      throw new RuntimeException("ExpirationDate is null");
    }
    content.put(VIDEO_EXPIRATION_DATE, expirationDate);
    return this;
  }

  /**
   * Removes the expiration date form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeExpirationDate() {
    content.remove(VIDEO_EXPIRATION_DATE);
    return this;
  }

  /**
   * The rating of the video. Allowed values are float numbers in the range 0.0 to 5.0.
   * 
   * @return
   */
  public Float getRating() {
    return (Float) content.get(VIDEO_RATING);
  }

  /**
   * The rating of the video. Allowed values are float numbers in the range 0.0 to 5.0.
   * 
   * @param rating
   * @return
   */
  public JavaSitemapGeneratorVideoURL setRating(float rating) {
    if (!check((int) (rating * 10), (int) (VIDEO_RATING_MAX_VALUE * 10))) {
      throw new RuntimeException("Rating with " + rating + " points invalid: " + rating);
    }
    content.put(VIDEO_RATING, rating);
    return this;
  }

  /**
   * Removes the rating form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeRating() {
    content.remove(VIDEO_RATING);
    return this;
  }

  /**
   * The number of times the video has been viewed.
   * 
   * @return
   */
  public Integer getViewCount() {
    return (Integer) content.get(VIDEO_VIEW_COUNT);
  }

  /**
   * The number of times the video has been viewed.
   * 
   * @param viewCount
   * @return
   */
  public JavaSitemapGeneratorVideoURL setViewCount(Integer viewCount) {
    if (!check(viewCount, Integer.MAX_VALUE)) {
      throw new RuntimeException("View count with " + viewCount + " views invalid: " + viewCount);
    }
    // everything is valid - so add
    content.put(VIDEO_VIEW_COUNT, viewCount);
    return this;
  }

  /**
   * Removes the view count form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeViewCount() {
    content.remove(VIDEO_VIEW_COUNT);
    return this;
  }

  /**
   * The date the video was first published, in W3C format.
   * 
   * @return
   */
  public Date getPublicationDate() {
    return (Date) content.get(VIDEO_PUBLICATION_DATE);
  }

  /**
   * The date the video was first published, in W3C format.
   * 
   * @param publicationDate
   * @return
   */
  public JavaSitemapGeneratorVideoURL setPublicationDate(Date publicationDate) {
    if (publicationDate == null) {
      throw new RuntimeException("PublicationDate is null");
    }
    content.put(VIDEO_PUBLICATION_DATE, publicationDate);
    return this;
  }

  /**
   * Removes the publication date form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removePublicationDate() {
    content.remove(VIDEO_PUBLICATION_DATE);
    return this;
  }

  /**
   * False if the video should be available only to users with SafeSearch turned off.
   * 
   * @return
   */
  public Boolean getFamilyFriendly() {
    return (Boolean) content.get(VIDEO_FAMILY_FRIENDLY);
  }

  /**
   * False if the video should be available only to users with SafeSearch turned off.
   * 
   * @param familyFriendly
   * @return
   */
  public JavaSitemapGeneratorVideoURL setFamilyFriendly(boolean familyFriendly) {
    content.put(VIDEO_FAMILY_FRIENDLY, familyFriendly);
    return this;
  }

  /**
   * Removes the family friedly tag form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeFamilyFriendly() {
    content.remove(VIDEO_FAMILY_FRIENDLY);
    return this;
  }

  /**
   * A tag associated with the video. Tags are generally very short descriptions of key concepts associated with a video or piece of content. A single video could have several tags, although it might belong to only one category. For example, a video about grilling food may belong in the Grilling category, but could be tagged "steak", "meat", "summer", and "outdoor". Create a new &lt;video:tag&gt; element for each tag associated with a video. A maximum of 32 tags is permitted.
   * 
   * @return
   */
  public String getTag() {
    return (String) content.get(VIDEO_TAG);
  }

  /**
   * A tag associated with the video. Tags are generally very short descriptions of key concepts associated with a video or piece of content. A single video could have several tags, although it might belong to only one category. For example, a video about grilling food may belong in the Grilling category, but could be tagged "steak", "meat", "summer", and "outdoor". Create a new &lt;video:tag&gt; element for each tag associated with a video. A maximum of 32 tags is permitted.
   * 
   * @param tags
   * @return
   */
  public JavaSitemapGeneratorVideoURL setTag(String tags) {
    // trim one time only
    tags = tags.trim();
    // throw an exception if we got invalid content
    checkInput(tags);
    // check if we stay to the google limits
    if (!check(tags.split(" ").length, VIDEO_TAG_MAX_COUNT)) {
      throw new RuntimeException("Tag with " + tags.trim().length() + " characters invalid: " + tags);
    }
    // everything is valid - so add
    content.put(VIDEO_TAG, getCDataString(tags));
    return this;
  }

  /**
   * Removes the tag tag form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeTag() {
    content.remove(VIDEO_TAG);
    return this;
  }

  /**
   * The video's category. For example, cooking. The value should be a string no longer than 256 characters. In general, categories are broad groupings of content by subject. Usually a video will belong to a single category. For example, a site about cooking could have categories for Broiling, Baking, and Grilling.
   * 
   * @return
   */
  public String getCategory() {
    return (String) content.get(VIDEO_CATEGORY);
  }

  /**
   * The video's category. For example, cooking. The value should be a string no longer than 256 characters. In general, categories are broad groupings of content by subject. Usually a video will belong to a single category. For example, a site about cooking could have categories for Broiling, Baking, and Grilling.
   * 
   * @param category
   * @return
   */
  public JavaSitemapGeneratorVideoURL setCategory(String category) {
    // throw an exception if we got invalid content
    checkInput(category);
    // check if we stay to the google limits
    if (!check(category.length(), VIDEO_CATEGORY_MAX_LENGTH)) {
      throw new RuntimeException("Category with " + category.length() + " characters invalid: " + category);
    }
    // everything is valid - so add
    content.put(VIDEO_CATEGORY, getCDataString(category));
    return this;
  }

  /**
   * Removes the category form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeCategory() {
    content.remove(VIDEO_CATEGORY);
    return this;
  }

  /**
   * A space-delimited list of countries where the video may or may not be played. Allowed values are country codes in ISO 3166 format. Only one &lt;video:restriction&gt; tag can appear for each video. If there is no &lt;video:restriction&gt; tag, it is assumed that the video can be played in all territories.<br>
   * <br>
   * The required attribute "relationship" specifies whether the video is restricted or permitted for the specified countries. Allowed values are allow or deny.
   * 
   * true = deny
   * false = allow
   * 
   * @return
   */
  public String getRestriction() {
    return (String) content.get(VIDEO_RESTRICTION);
  }

  /**
   * A space-delimited list of countries where the video may or may not be played. Allowed values are country codes in ISO 3166 format. Only one &lt;video:restriction&gt; tag can appear for each video. If there is no &lt;video:restriction&gt; tag, it is assumed that the video can be played in all territories.<br>
   * <br>
   * The required attribute "relationship" specifies whether the video is restricted or permitted for the specified countries. Allowed values are allow or deny.
   * 
   * true = deny
   * false = allow
   * 
   * @return
   */
  public String getRestrictionAttributes() {
    return attributes.get(VIDEO_RESTRICTION);
  }

  /**
   * A space-delimited list of countries where the video may or may not be played. Allowed values are country codes in ISO 3166 format. Only one &lt;video:restriction&gt; tag can appear for each video. If there is no &lt;video:restriction&gt; tag, it is assumed that the video can be played in all territories.<br>
   * <br>
   * The required attribute "relationship" specifies whether the video is restricted or permitted for the specified countries. Allowed values are allow or deny.
   * 
   * @ToDo Build up iso 3166 country code list
   * 
   * @param restriction
   * @return
   */
  public JavaSitemapGeneratorVideoURL setRestriction(String restriction, VIDEO_RESTRICTION_RELATIONSHIP relationship) {
    // throw an exception if we got invalid content
    checkInput(restriction);
    // check if all countries are in iso 3166 encoding
    for (String iso3166Country : restriction.split(" ")) {
      if (!iso3166Country.matches("[a-zA-Z]{2}")) {
        throw new RuntimeException("Invalid country ISO 3166 country code: \"" + iso3166Country + "\"");
      }
    }
    // everything is valid - so add
    content.put(VIDEO_RESTRICTION, getCDataString(restriction));
    attributes.put(VIDEO_RESTRICTION, getAttributeContentFor(VIDEO_RESTRICTION, "relationship", relationship.toString().toLowerCase()));
    return this;
  }

  /**
   * Removes the restriction tag form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeRestriction() {
    content.remove(VIDEO_RESTRICTION);
    attributes.remove(VIDEO_RESTRICTION);
    return this;
  }

  /**
   * A link to the gallery (collection of videos) in which this video appears. Only one &lt;video:gallery_loc&gt; tag can be listed for each video. The optional attribute title indicates the title of the gallery.
   * 
   * @return
   */
  public URL getGalleryLoc() {
    return (URL) content.get(VIDEO_GALLERY_LOC);
  }

  /**
   * A link to the gallery (collection of videos) in which this video appears. Only one &lt;video:gallery_loc&gt; tag can be listed for each video. The optional attribute title indicates the title of the gallery.
   * 
   * @return
   */
  public URL getGalleryLocation() {
    return getGalleryLoc();
  }

  /**
   * A link to the gallery (collection of videos) in which this video appears. Only one &lt;video:gallery_loc&gt; tag can be listed for each video. The optional attribute title indicates the title of the gallery.
   * 
   * @param galleryLocation - Set to null to ignore this
   * @return
   */
  public JavaSitemapGeneratorVideoURL setGalleryLoc(URL galleryLocation) {
    if (galleryLocation == null) {
      throw new RuntimeException("GalleryLocation is null");
    }

    content.put(VIDEO_GALLERY_LOC, galleryLocation);
    return this;
  }

  /**
   * A link to the gallery (collection of videos) in which this video appears. Only one &lt;video:gallery_loc&gt; tag can be listed for each video. The optional attribute title indicates the title of the gallery.
   * 
   * @param galleryLocation
   * @return
   */
  public JavaSitemapGeneratorVideoURL setGalleryLocation(URL galleryLocation) {
    return setGalleryLoc(galleryLocation);
  }

  /**
   * A link to the gallery (collection of videos) in which this video appears. Only one &lt;video:gallery_loc&gt; tag can be listed for each video. The optional attribute title indicates the title of the gallery.
   * 
   * @param galleryLocation
   * @return
   * @throws MalformedURLException
   */
  public JavaSitemapGeneratorVideoURL setGalleryLoc(String galleryLocation) throws MalformedURLException {
    return setGalleryLoc(new URL(galleryLocation));
  }

  /**
   * A link to the gallery (collection of videos) in which this video appears. Only one &lt;video:gallery_loc&gt; tag can be listed for each video. The optional attribute title indicates the title of the gallery.
   * 
   * @param galleryLocation
   * @return
   * @throws MalformedURLException
   */
  public JavaSitemapGeneratorVideoURL setGalleryLocation(String galleryLocation) throws MalformedURLException {
    return setGalleryLoc(galleryLocation);
  }

  /**
   * Removes the GalleryLocation tag form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeGalleryLocation() {
    return removeGalleryLoc();
  }

  /**
   * Removes the GalleryLocation tag form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeGalleryLoc() {
    content.remove(VIDEO_GALLERY_LOC);
    attributes.remove(VIDEO_GALLERY_LOC);
    return this;
  }

  /**
   * The optional attribute title indicates the title of the gallery.
   * 
   * @param title
   * @return
   */
  public String getGalleryLocTitleAttributes() {
    return attributes.get(VIDEO_GALLERY_LOC);
  }

  /**
   * The optional attribute title indicates the title of the gallery.
   * 
   * @param title
   * @return
   */
  public String getGalleryLocationTitleAttributes() {
    return getGalleryLocTitleAttributes();
  }

  /**
   * The optional attribute title indicates the title of the gallery.
   * 
   * @param title
   * @return
   */
  public JavaSitemapGeneratorVideoURL setGalleryLocTitle(String title) {
    // throw an exception if we got invalid content
    checkInput(title);

    attributes.put(VIDEO_GALLERY_LOC, getAttributeContentFor(VIDEO_GALLERY_LOC, "title", title, false));
    return this;
  }

  /**
   * The optional attribute title indicates the title of the gallery.
   * 
   * @param title
   * @return
   */
  public JavaSitemapGeneratorVideoURL setGalleryLocationTitle(String title) {
    return setGalleryLocTitle(title);
  }

  /**
   * Removes the GalleryLocation tag form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeGalleryLocAttributes() {
    attributes.remove(VIDEO_GALLERY_LOC);
    return this;
  }

  /**
   * Removes the GalleryLocationAttributes tag form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeGalleryLocationAttributes() {
    return removeGalleryLocAttributes();
  }

  /**
   * The price to download or view the video. Do not use this tag for free videos.<br>
   * The required attribute currency specifies the currency in ISO 4217 format.<br>
   * e.g.: 5.99; 1499.50; <br>
   * invalid: 1,499.00; 10,000,12<br>
   * <br>
   * <b>Retrieve the following price attributes with getPriceAttributes():</b><br>
   * <br>
   * The optional attribute type specifies the purchase option. Allowed values are rent and own. If this is not specified, the default value is own.<br>
   * <br>
   * The optional attribute resolution specifies the purchased resolution. Allows values are HD and SD.<br>
   * <br>
   * More than one &lt;video:price&gt; element can be listed (for example, in order to specify various currencies, purchasing options, or resolutions).<br>
   * <br>
   * 
   * @ToDo: build in: http://blog.noblemaster.com/2012/11/01/iso-4217-currency-list-currency-java/
   */
  public List<String> getPrice() {
    return prices;
  }

  /**
   * Store for:
   * The optional attribute type specifies the purchase option. Allowed values are rent and own. If this is not specified, the default value is own.<br>
   * <br>
   * The optional attribute resolution specifies the purchased resolution. Allows values are HD and SD.<br>
   * <br>
   * More than one &lt;video:price&gt; element can be listed (for example, in order to specify various currencies, purchasing options, or resolutions).<br>
   * <br>
   * 
   * @ToDo: build in: http://blog.noblemaster.com/2012/11/01/iso-4217-currency-list-currency-java/
   */
  public List<String> getPriceAttributes() {
    return pricesAttributes;
  }

  /**
   * 
   * The price to download or view the video. Do not use this tag for free videos.<br>
   * The required attribute currency specifies the currency in ISO 4217 format.<br>
   * e.g.: 5.99; 1499.50; <br>
   * invalid: 1,499.00; 10,000,12<br>
   * <br>
   * The optional attribute type specifies the purchase option. Allowed values are rent and own. If this is not specified, the default value is own.<br>
   * <br>
   * The optional attribute resolution specifies the purchased resolution. Allows values are HD and SD.<br>
   * <br>
   * More than one &lt;video:price&gt; element can be listed (for example, in order to specify various currencies, purchasing options, or resolutions).<br>
   * <br>
   * 
   * @ToDo: build in: http://blog.noblemaster.com/2012/11/01/iso-4217-currency-list-currency-java/
   */
  public JavaSitemapGeneratorVideoURL setPrice(float price, String currency) {
    // set the price with the normal method
    return setPrice(price, currency, null, null);
  }

  /**
   * 
   * The price to download or view the video. Do not use this tag for free videos.<br>
   * The required attribute currency specifies the currency in ISO 4217 format.<br>
   * e.g.: 5.99; 1499.50; <br>
   * invalid: 1,499.00; 10,000,12<br>
   * <br>
   * The optional attribute type specifies the purchase option. Allowed values are rent and own. If this is not specified, the default value is own.<br>
   * <br>
   * The optional attribute resolution specifies the purchased resolution. Allows values are HD and SD.<br>
   * <br>
   * More than one &lt;video:price&gt; element can be listed (for example, in order to specify various currencies, purchasing options, or resolutions).<br>
   * <br>
   * 
   * @ToDo: build in: http://blog.noblemaster.com/2012/11/01/iso-4217-currency-list-currency-java/
   */
  public JavaSitemapGeneratorVideoURL setPrice(float price, String currency, VIDEO_PRICE_TYPE type) {
    // set the price with the normal method
    return setPrice(price, currency, type, null);
  }

  /**
   * 
   * The price to download or view the video. Do not use this tag for free videos.<br>
   * The required attribute currency specifies the currency in ISO 4217 format.<br>
   * e.g.: 5.99; 1499.50; <br>
   * invalid: 1,499.00; 10,000,12<br>
   * <br>
   * The optional attribute type specifies the purchase option. Allowed values are rent and own. If this is not specified, the default value is own.<br>
   * <br>
   * The optional attribute resolution specifies the purchased resolution. Allows values are HD and SD.<br>
   * <br>
   * More than one &lt;video:price&gt; element can be listed (for example, in order to specify various currencies, purchasing options, or resolutions).<br>
   * <br>
   * 
   * @ToDo: build in: http://blog.noblemaster.com/2012/11/01/iso-4217-currency-list-currency-java/
   */
  public JavaSitemapGeneratorVideoURL setPrice(float price, String currency, VIDEO_PRICE_RESOLUTION resolution) {
    // set the price with the normal method
    return setPrice(price, currency, null, resolution);
  }

  /**
   * 
   * The price to download or view the video. Do not use this tag for free videos.<br>
   * The required attribute currency specifies the currency in ISO 4217 format.<br>
   * e.g.: 5.99; 1499.50; <br>
   * invalid: 1,499.00; 10,000,12<br>
   * <br>
   * The optional attribute type specifies the purchase option. Allowed values are rent and own. If this is not specified, the default value is own.<br>
   * <br>
   * The optional attribute resolution specifies the purchased resolution. Allows values are HD and SD.<br>
   * <br>
   * More than one &lt;video:price&gt; element can be listed (for example, in order to specify various currencies, purchasing options, or resolutions).<br>
   * <br>
   * 
   * @ToDo: build in: http://blog.noblemaster.com/2012/11/01/iso-4217-currency-list-currency-java/
   */
  public JavaSitemapGeneratorVideoURL setPrice(float price, String currency, VIDEO_PRICE_TYPE type, VIDEO_PRICE_RESOLUTION resolution) {

    // throw an exception if we got invalid content
    checkInput(currency);
    // check if we stay to the ISO 4217 format - 3 characters limit
    if (!currency.trim().matches("[[a-z][A-Z]]{3}")) {
      throw new RuntimeException("Currency invalid: " + currency.trim());
    }

    // price is set - so remember for the output!
    // the output differs so we just fill the map
    // and the for each loop will trigger price
    content.put(VIDEO_PRICE, "");

    // okay, we need prices - so get the memory
    if (prices == null) {
      prices = new ArrayList<String>();
      pricesAttributes = new ArrayList<String>();
    }

    // add the price to our price list
    prices.add(priceFormat.format(price));

    // now build the attributes
    // we store this temp here so getAttributeContentFor will work fine
    attributes.put(VIDEO_PRICE, getAttributeContentFor(VIDEO_PRICE, "currency", currency, false));
    if (type != null)
      attributes.put(VIDEO_PRICE, getAttributeContentFor(VIDEO_PRICE, "type", type.toString()));
    if (resolution != null)
      attributes.put(VIDEO_PRICE, getAttributeContentFor(VIDEO_PRICE, "resolution", resolution.toString()));

    // now store this into prices attributes
    pricesAttributes.add(attributes.get(VIDEO_PRICE));
    // and clear the normal attribute
    attributes.remove(VIDEO_PRICE);

    return this;
  }

  /**
   * Removes one of the price tag form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removePrice(int position) {
    prices.remove(position);
    pricesAttributes.remove(position);
    return this;
  }

  /**
   * Indicates whether a subscription (either paid or free) is required to view the video. Allowed values are yes or no.
   * 
   * @return
   */
  public Boolean getRequiresSubscription() {
    return (Boolean) content.get(VIDEO_REQUIRES_SUBSCRIPTION);
  }

  /**
   * Indicates whether a subscription (either paid or free) is required to view the video. Allowed values are yes or no.
   * 
   * @param requiresSubscription
   * @return
   */
  public JavaSitemapGeneratorVideoURL setRequiresSubscription(boolean requiresSubscription) {
    content.put(VIDEO_REQUIRES_SUBSCRIPTION, requiresSubscription);
    return this;
  }

  /**
   * Removes the RequiresSubscription tag form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeRequiresSubscription() {
    content.remove(VIDEO_REQUIRES_SUBSCRIPTION);
    return this;
  }

  /**
   * The video uploader's name. Only one &lt;video:uploader&gt; is allowed per video.<br>
   * The optional attribute info specifies the URL of a webpage with additional information about this uploader. This URL must be on the same domain as the &lt;loc&gt; tag.<br>
   * 
   * @param uploader
   */
  public String getUploader() {
    return (String) content.get(VIDEO_UPLOADER);
  }

  /**
   * The video uploader's name. Only one &lt;video:uploader&gt; is allowed per video.<br>
   * The optional attribute info specifies the URL of a webpage with additional information about this uploader. This URL must be on the same domain as the &lt;loc&gt; tag.<br>
   * 
   * @param uploader
   */
  public String getUploaderAttributes() {
    return (String) attributes.get(VIDEO_UPLOADER);
  }

  /**
   * The video uploader's name. Only one &lt;video:uploader&gt; is allowed per video.<br>
   * The optional attribute info specifies the URL of a webpage with additional information about this uploader. This URL must be on the same domain as the &lt;loc&gt; tag.<br>
   * 
   * @param uploader
   */
  public JavaSitemapGeneratorVideoURL setUploader(String uploader) {
    // throw an exception if we got invalid content
    checkInput(uploader);

    // everything is valid - so add
    content.put(VIDEO_UPLOADER, uploader);
    return this;
  }

  /**
   * The optional attribute info specifies the URL of a webpage with additional information about this uploader. This URL must be on the same domain as the &lt;loc&gt; tag.<br>
   * 
   * @param info
   * @return
   */
  public JavaSitemapGeneratorVideoURL setUploaderInfo(URL info) {
    if (info == null) {
      throw new RuntimeException("UploaderInfo is null");
    }

    // everything is valid - so add
    attributes.put(VIDEO_UPLOADER, getAttributeContentFor(VIDEO_UPLOADER, "info", info.toString(), false));
    return this;
  }

  /**
   * The optional attribute info specifies the URL of a webpage with additional information about this uploader. This URL must be on the same domain as the &lt;loc&gt; tag.<br>
   * 
   * @param info
   * @return
   * @throws MalformedURLException
   */
  public JavaSitemapGeneratorVideoURL setUploaderInfo(String info) throws MalformedURLException {
    return setUploaderInfo(new URL(info));
  }

  /**
   * Removes the uploader tag form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeUploader() {
    content.remove(VIDEO_UPLOADER);
    attributes.remove(VIDEO_UPLOADER);
    return this;
  }

  /**
   * Removes the uploader attributes form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeUploaderAttributes() {
    attributes.remove(VIDEO_UPLOADER);
    return this;
  }

  /**
   * A list of space-delimited platforms where the video may or may not be played. Allowed values are web, mobile, and tv. Only one &lt;video:platform&gt; tag can appear for each video. If there is no &lt;video:platform&gt; tag, it is assumed that the video can be played on all platforms.<br>
   * The required attribute relationship specifies whether the video is restricted or permitted for the specified platforms. Allowed values are allow or deny.
   * 
   * @return
   */
  public List<VIDEO_PLATFORM_TAGS> getPlatformList() {

    List<VIDEO_PLATFORM_TAGS> platformTags = new ArrayList<VIDEO_PLATFORM_TAGS>();

    if (getPlatform() != null) {
      for (String platform : getPlatform().split(" ")) {
        platformTags.add(VIDEO_PLATFORM_TAGS.valueOf(platform.toUpperCase()));
      }
    }

    return platformTags;
  }

  /**
   * A list of space-delimited platforms where the video may or may not be played. Allowed values are web, mobile, and tv. Only one &lt;video:platform&gt; tag can appear for each video. If there is no &lt;video:platform&gt; tag, it is assumed that the video can be played on all platforms.<br>
   * The required attribute relationship specifies whether the video is restricted or permitted for the specified platforms. Allowed values are allow or deny.
   * 
   * @return
   */
  public String getPlatform() {
    return (String) content.get(VIDEO_PLATFORM);
  }

  /**
   * A list of space-delimited platforms where the video may or may not be played. Allowed values are web, mobile, and tv. Only one &lt;video:platform&gt; tag can appear for each video. If there is no &lt;video:platform&gt; tag, it is assumed that the video can be played on all platforms.<br>
   * The required attribute relationship specifies whether the video is restricted or permitted for the specified platforms. Allowed values are allow or deny.
   * 
   * @param platform SitemapGeneratorVideoURL.VIDEO_PLATFORM_TAGS
   * @return
   */
  public JavaSitemapGeneratorVideoURL setPlatform(VIDEO_PLATFORM_TAGS platform) {

    if (platform == null) {
      throw new RuntimeException("Platform is null");
    }

    // get is a string - to lower case it in one placce
    String p = platform.toString().toLowerCase();

    if (getPlatform() != null) {
      // only add - if this tag is not already set
      if (!getPlatform().contains(p)) {
        content.put(VIDEO_PLATFORM, getPlatform() + " " + p);
      }
    } else {
      content.put(VIDEO_PLATFORM, p);
    }
    return this;
  }

  /**
   * A list of space-delimited platforms where the video may or may not be played. Allowed values are web, mobile, and tv. Only one &lt;video:platform&gt; tag can appear for each video. If there is no &lt;video:platform&gt; tag, it is assumed that the video can be played on all platforms.<br>
   * The required attribute relationship specifies whether the video is restricted or permitted for the specified platforms. Allowed values are allow or deny.
   * 
   * @param platform List<SitemapGeneratorVideoURL.VIDEO_PLATFORM_TAGS>
   * @return
   */
  public JavaSitemapGeneratorVideoURL setPlatform(List<VIDEO_PLATFORM_TAGS> platforms) {
    for (VIDEO_PLATFORM_TAGS tag : platforms) {
      setPlatform(tag);
    }
    return this;
  }

  /**
   * Removes all the platform tags form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removePlatform() {
    content.remove(VIDEO_PLATFORM);
    return this;
  }

  /**
   * Indicates whether the video is a live stream. Allowed values are yes or no.
   * 
   * @return
   */
  public Boolean getLive() {
    return (Boolean) content.get(VIDEO_LIVE);
  }

  /**
   * Indicates whether the video is a live stream. Allowed values are true (yes) or no false (no).<br>
   * <br>
   * true = yes<br>
   * false = no<br>
   * <br>
   * 
   * @param live
   * @return
   */
  public JavaSitemapGeneratorVideoURL setLive(boolean live) {
    content.put(VIDEO_LIVE, live);
    return this;
  }

  /**
   * Removes the live tags form the sitemap
   * 
   * @return
   */
  public JavaSitemapGeneratorVideoURL removeLive() {
    content.remove(VIDEO_LIVE);
    return this;
  }

  /**
   * Returns the xmlns for the sitemap
   * And of course, its always the same!
   */
  public XMLNS getXmlns() {
    return XMLNS.SITEMAPVIDEO11;
  }

  /**
   * Export this URL to a little proper xml fragment for our image url
   */
  public String toXMLFragment() {

    // we will return the String from the builder
    StringBuilder sb = new StringBuilder("    <video:video>\n");

    for (Entry<String, Object> entry : content.entrySet()) {
      if (entry.getKey().equals(VIDEO_EXPIRATION_DATE) || entry.getKey().equals(VIDEO_PUBLICATION_DATE)) {
        sb.append("      <video:" + entry.getKey() + ">" + StringJavaSitemapGenerator.getSimpleDateFormat().format(entry.getValue()) + "</video:" + entry.getKey() + ">\n");
      } else {
        if (entry.getKey().equals(VIDEO_FAMILY_FRIENDLY) || entry.getKey().equals(VIDEO_LIVE) || entry.getKey().equals(VIDEO_REQUIRES_SUBSCRIPTION)) {
          sb.append("      <video:" + entry.getKey() + ">" + ((Boolean) (entry.getValue()) ? "yes" : "no") + "</video:" + entry.getKey() + ">\n");
        } else {
          if (entry.getKey().equals(VIDEO_PRICE)) {
            for (int idx = 0; idx < prices.size(); idx++) {
              sb.append("      <video:" + entry.getKey() + " " + pricesAttributes.get(0) + ">" + prices.get(idx) + "</video:" + entry.getKey() + ">\n");
            }
          } else {
            if (attributes.get(entry.getKey()) == null) {
              sb.append("      <video:" + entry.getKey() + ">" + entry.getValue().toString() + "</video:" + entry.getKey() + ">\n");
            } else {
              sb.append("      <video:" + entry.getKey() + " " + attributes.get(entry.getKey()) + ">" + entry.getValue().toString() + "</video:" + entry.getKey() + ">\n");
            }
          }
        }
      }
    }

    // the url part
    sb.append("    </video:video>\n");

    // return the xml framgent for this image url
    return sb.toString();
  }

  /**
   * Just check a simple check
   * 
   * @param expect
   * @param really
   * @return
   */
  private boolean check(int really, int expect) {
    if (really <= expect && really >= 0) {
      return true;
    }
    return false;
  }

  /**
   * Checks an input:<br>
   * <ul>
   * <li>is null</li>
   * <li>is ""</li>
   * <li>hasSpecialChars</li>
   * </ul>
   * <br>
   * --> if this is true -> throws runtime exception!<br>
   * 
   * @param input
   */
  protected void checkInput(String input) {
    // check if we got valid values
    if (input == null || "".equals(input) || hasSpecialChars(input)) {
      throw new RuntimeException("Input is invalid " + input);
    }
  }

  /**
   * Retruns if the given string is a plain string<br>
   * so we can put it directly into the sitemap<br>
   * Use for video title and description<br>
   * <br>
   * With this method you can check, if your title or your description will be wrapped into cdata blocks<br>
   * true - cdata blocks<br>
   * false - no cdata blocks<br>
   * 
   * @param content
   * @return
   */
  public static boolean hasSpecialChars(String content) {
    return !content.matches("[\\w =,.!?()/+\\-_\\\\]+");
  }

  /**
   * Checks for the occurence of bad characters<br>
   * allowed characters are: [\\w =,.!?()/+\\-_\\\\]+
   * 
   * And retruns a CDATA string if necessary
   * 
   * @param content
   * @return
   */
  public String getCDataString(String content) {
    if (!hasSpecialChars(content) || content.contains("<![CDATA[") && content.contains("]]>")) {
      return content;                             // cdata is there or not needed
    } else {
      if (!content.contains(CDATA_START) && !content.contains(CDATA_END)) {
        return CDATA_START + content + CDATA_END;       // cdata is needed
      } else {
        throw new RuntimeException("Unsupported content ?? (remove invalid cdata part blocks like ]]> or <![CDATA[): " + content);
      }
    }
  }

  // && )

  /**
   * Writes the attributes correctly!
   * e.g.: &lt;video:restriction relationship="allow"&gt;IE GB US CA&lt;/video:restriction&gt;
   * --> will write/override the relationship attribute
   * 
   * @param attributeType
   * @param attributeTagName
   * @param attributeTagContent
   * @param toLower - always true here
   * @return
   */
  private String getAttributeContentFor(String attributeType, String attributeTagName, String attributeTagContent) {
    return getAttributeContentFor(attributeType, attributeTagName, attributeTagContent, true);
  }

  /**
   * Writes the attributes correctly!
   * e.g.: &lt;video:restriction relationship="allow"&gt;IE GB US CA&lt;/video:restriction&gt;
   * --> will write/override the relationship attribute
   * 
   * @param attributeType
   * @param attributeTagName
   * @param attributeTagContent
   * @param toLower - lower the attribute tag content
   * @return
   */
  private String getAttributeContentFor(String attributeType, String attributeTagName, String attributeTagContent, boolean toLower) {

    // to lower the attribute?
    if (toLower) {
      attributeTagContent = attributeTagContent.trim().toLowerCase();
    } else {
      attributeTagContent = attributeTagContent.trim();
    }

    // if we already have stored this attribute
    if (attributes.containsKey(attributeType)) {
      // check if we already got this attribute tag
      if (attributes.get(attributeType).contains(attributeTagName)) {
        // yes, so we have to replace it!
        // we need the string from the beginning attributeTagName till the end "
        String subString = attributes.get(attributeType).substring(attributes.get(attributeType).indexOf(attributeTagName + "=\""), attributes.get(attributeType).indexOf("\"", attributes.get(attributeType).indexOf(attributeTagName + "=\"") + (attributeTagName + "=\"").length()) + 1);
        // return the right attribute combination
        return attributes.get(attributeType).replace(subString, attributeTagName + "=\"" + attributeTagContent + "\"");
      } else {
        // we have content to add
        return attributes.get(attributeType) + " " + attributeTagName + "=\"" + attributeTagContent + "\"";
      }
    } else {
      return attributeTagName + "=\"" + attributeTagContent + "\"";
    }
  }
}
